package com.henriquevilela.drumtrainer.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiUnavailableException;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JSlider;
import javax.swing.JToolBar;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import com.henriquevilela.drumtrainer.core.DrumTrainerEngine;
import com.henriquevilela.drumtrainer.core.DrumTrainerEvent;
import com.henriquevilela.drumtrainer.core.DrumTrainerListener;
import com.henriquevilela.drumtrainer.core.PlaySymbol;
import com.henriquevilela.drumtrainer.core.Report;

/**
 * @author Henrique de Souza Vilela
 * henrique.vilela@gmail.com
 */
public class DrumTrainerGui extends JFrame implements DrumTrainerListener, ChangeListener {

	protected static final long serialVersionUID = 1L;

	protected Action fileOpenAction;
	protected Action playStopAction;
	protected Action reloadAction;
	protected Action exitAction;
	protected Action aboutAction;

	protected File lastFile;
	
	protected DrumTrainerEngine engine;

	protected JProgressBar progress;
	protected JSlider speed;
	protected JPanel workPanel;

	// Workspace
	protected int startIndex = 0; // to optimize the symbols draw
	protected double timeWidth = 0.0004;
	protected int playPosition = 200;
	protected List<PlaySymbol> playSymbolList;
	protected long time = 0;

	// tracks panel

	protected JPanel tracksPanel;
	protected Map<JCheckBox, Integer> tracksMap;

	public DrumTrainerGui() throws MidiUnavailableException {
		super(Messages.getString("appName"));

		setLayout(new BorderLayout());
		setPreferredSize(new Dimension(800, 600));

		engine = new DrumTrainerEngine();
		engine.addDrumEngineListener(this);

		fileOpenAction = new AbstractAction(Messages.getString("open")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent event) {
				open();
			}
		};

		playStopAction = new AbstractAction(Messages.getString("playStop")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent event) {
				playStop();
			}
		};
		playStopAction.setEnabled(false);

		reloadAction = new AbstractAction(Messages.getString("reload")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent event) {
				try {
					loadFile(lastFile);
				} catch (Exception exception) {
					showError(exception);
				}
			}
		};
		reloadAction.setEnabled(false);
		
		exitAction = new AbstractAction(Messages.getString("exit")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent event) {
				exit();
			}
		};
		
		aboutAction = new AbstractAction(Messages.getString("about")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent event) {
				openAboutWindow();
			}
		};

		// tool bar
		JToolBar toolBar = new JToolBar();
		toolBar.add(fileOpenAction);
		toolBar.add(playStopAction);
		toolBar.add(reloadAction);

		toolBar.addSeparator();

		progress = new JProgressBar();
		toolBar.add(progress);

		toolBar.addSeparator();

		speed = new JSlider(10, 200, 100);
		speed.addChangeListener(this);
		toolBar.add(speed);

		add(toolBar, BorderLayout.PAGE_START);

		// menu
		JMenuBar menuBar = new JMenuBar();

		JMenu fileMenu = new JMenu(Messages.getString("file"));
		fileMenu.add(fileOpenAction);
		fileMenu.add(reloadAction);
		fileMenu.addSeparator();
		fileMenu.add(exitAction);
		menuBar.add(fileMenu);
		
		JMenu helpMenu = new JMenu(Messages.getString("help"));
		helpMenu.add(aboutAction);
		menuBar.add(helpMenu);
		
		setJMenuBar(menuBar);
		
		workPanel = new JPanel() {
			private static final long serialVersionUID = 1L;

			@Override
			public void paint(Graphics graphics) {
				super.paint(graphics);

				paintWorkspace(graphics);
			}
		};

		add(workPanel, BorderLayout.CENTER);

		tracksPanel = new JPanel();
		tracksPanel.setLayout(new GridLayout(10, 1));

		add(tracksPanel, BorderLayout.LINE_END);

		playSymbolList = new Vector<PlaySymbol>();

		pack();

		selectDevice();
	}

	protected void paintWorkspace(Graphics graphics) {
		graphics.setColor(new Color(0x999999));
		for (int i = 1; i < 5; i++) {
			graphics.drawLine(i * getWidth() / 6, 0, i * getWidth() / 6, getHeight());
		}

		graphics.setColor(new Color(0xFF0000));
		graphics.drawLine(0, getHeight() - playPosition, getWidth(), getHeight() - playPosition);

		for (int i = 0; i < playSymbolList.size(); i++) {
			PlaySymbol playSymbol = playSymbolList.get(i);

			int x = (int)(getWidth() / 6 * (playSymbol.getSymbol().getColumn() + 0.5));
			int y = getHeight() - (int) (timeWidth * (playSymbol.getTime() - time)) - playPosition;

			if (y < 0) {
				break;
			} else if (y > getHeight()) {
				startIndex = i + 1;
			}

			graphics.setColor(playSymbol.getSymbol().getColor());

			if (playSymbol.getSymbol().getColumn() == 5) {
				int height = 10;

				graphics.fillRect(0, y - height / 2, getWidth(), height);
			} else {
				int width = 60;
				int height = 35;
				
				graphics.fillOval(x - width / 2, y - height / 2, width, height);
			}
		}
	}
	
	public void stateChanged(ChangeEvent event) {
		if (event.getSource() == speed) {
			engine.setTempoFactor(speed.getValue());
		} else if (tracksMap.containsKey(event.getSource())) {
			engine.setTrackMute(tracksMap.get(event.getSource()), !((JCheckBox) event.getSource()).isSelected());
		}
	}

	private void selectDevice() {
		Object[] devices = engine.getAvaliableDeviceList().toArray();
		MidiDevice.Info deviceInfo = (MidiDevice.Info) JOptionPane.showInputDialog(this, Messages.getString("selectInput"), Messages.getString("selectInput"), JOptionPane.PLAIN_MESSAGE, null, devices, devices[0]);

		if (deviceInfo == null) {
			System.exit(0);
		}

		try {
			engine.setInputDevice(deviceInfo);
		} catch (MidiUnavailableException exception) {
			exception.printStackTrace();

			showError(exception);

			System.exit(0);
		}
	}

	protected void open() {
		JFileChooser fileChooser = new JFileChooser(new File("./midi"));
		fileChooser.showOpenDialog(this);

		File selectedFile = fileChooser.getSelectedFile();

		if (selectedFile != null) {
			loadFile(selectedFile);
		}
	}

	protected void loadFile(File selectedFile) {
		lastFile = selectedFile;
		reloadAction.setEnabled(true);
		
		startIndex = 0;
		playStopAction.setEnabled(false);
		playSymbolList = new Vector<PlaySymbol>();

		speed.setValue(100);

		try {
			playSymbolList = engine.loadFile(selectedFile);

			tracksMap = new HashMap<JCheckBox, Integer>();
			tracksPanel.removeAll();
			for (int i = 0; i < engine.getTracksLenght(); i++) {
				JCheckBox trackCheckBox = new JCheckBox(Messages.getString("track") + i, true);
				tracksPanel.add(trackCheckBox);

				trackCheckBox.addChangeListener(this);

				tracksMap.put(trackCheckBox, i);
			}

			pack();

			time = 0;
			playStopAction.setEnabled(true);
		} catch (Exception exception) {
			showError(exception);
		}

		setTitle(Messages.getString("appName") + " - " + selectedFile.getName());

		workPanel.repaint();
	}

	protected void playStop() {
		try {
			if (engine.isPlaying()) {
				engine.stop();
			} else {
				engine.play();
			}
		} catch (Exception exception) {
			showError(exception);
		}
	}

	/**
	 * @see com.henriquevilela.drumtrainer.core.DrumTrainerListener#tick(com.henriquevilela.drumtrainer.core.DrumTrainerEvent)
	 */
	public void tick(DrumTrainerEvent event) {
		time = event.getTime();
		workPanel.repaint();

		progress.setValue((int) (time * 100 / engine.getMicrosecondLength()));
	}

	/**
	 * @see com.henriquevilela.drumtrainer.core.DrumTrainerListener#songEnd(com.henriquevilela.drumtrainer.core.Report)
	 */
	public void songEnd(Report report) {
		try {
			progress.setValue(100);

			new ReportWindow(this, report).setVisible(true);
		} catch (Throwable throwable) {
			showError(Messages.getString("jfreechartProblem"));
		}
	}

	protected void showError(Throwable throwable) {
		showError(throwable.getClass().getSimpleName() + ":" + throwable.getMessage());
	}

	private void showError(String message) {
		JOptionPane.showMessageDialog(this, message);
	}

	@Override
	protected void processWindowEvent(WindowEvent event) {
		if (event.getID() == WindowEvent.WINDOW_CLOSING) {
			exit();
		} else {
			super.processWindowEvent(event);
		}
	}

	protected void openAboutWindow() {
		new AboutWindow(this).setVisible(true);
	}
	
	protected void exit() {
		engine.stop();
		
		int option = JOptionPane.showConfirmDialog(this, Messages.getString("whatExit"), Messages.getString("confirmation"), JOptionPane.YES_NO_OPTION);

		if (option == JOptionPane.YES_OPTION) {
			engine.stop();
			System.exit(0);
		}
	}

	public static void main(String[] args) throws MidiUnavailableException {
		JFrame window = new DrumTrainerGui();
		window.setVisible(true);
	}
}